#include "Framework.h"
#include "../Environment/Environment.h"
#include "../Selection/Selection.h"
#include "../Crossover/Crossover.h"
#include "../Worker/Crossover/CrossoverWorker.h"
#include "../Worker/EvaluateFitness/EvaluateFitnessWorker.h"
#include "../Job/Crossover/CrossoverJob.h"
#include "../Job/EvaluateFitness/EvaluateFitnessJob.h"
#include "../Worker/Crossover/Local/LocalCrossoverWorker.h"
#include "../Worker/EvaluateFitness/Local/LocalEvaluateFitnessWorker.h"

Framework::Framework(Environment* simulation, Crossover* crosser, 
                     Selection* selector, map<string, double>& options)
{
    generationCount = 0;
    targetGenerationCount = 0;
    completedEvaluateFitnessJobs = 0;
    evaluateFitnessWorker = NULL;
    crossoverWorker = NULL;
    limitedRunFlag = false;
    
    this->simulation = simulation;
    this->crosser = crosser;
    this->selector = selector;
    
    //provide some default parameters in case they are 
    //not defined in the options hash
    topResultsSize = 10;
    populationSize = 50;
    historySize = 500;
    
    if (options.find("topResultsSize") != options.end())
        topResultsSize = (int) options["topResultsSize"];
    
    if (options.find("populationSize") != options.end())
        populationSize = (int) options["populationSize"];
    
    if (options.find("historySize") != options.end())
        historySize = (int) options["historySize"];
        
    simulation->generateRandomPopulation(newPopulation, populationSize);
    preloadEvaluateFitnessJobs();
    preloadBestSolutions();
    preloadHistory();
}

Framework::~Framework() 
{
    cout << "starting framework destructor!" << endl;
}

/**
 * Calling the stop method requests that the framework's internal worker thread
 * begin execution.  This function blocks until the worker thread begins running.
 */
void Framework::start() 
{
//    cout << "Framework.start() called!" << endl;
    if (evaluateFitnessWorkers.size() == 0) 
    {
        evaluateFitnessWorker = new LocalEvaluateFitnessWorker();
        registerWorker(evaluateFitnessWorker);
        evaluateFitnessWorkerThread = new ZThread::Thread(evaluateFitnessWorker);
    }
    
    if (crossoverWorkers.size() == 0) 
    {
        crossoverWorker = new LocalCrossoverWorker();
        registerWorker(crossoverWorker);
        crossoverWorkerThread = new ZThread::Thread(crossoverWorker);
    }
    
    frameworkWorker = new FrameworkWorker(this);
    workerThread = new ZThread::Thread(frameworkWorker);
}

/**
 * Calling the stop method requests that the framework's internal worker thread
 * halt execution.  This function blocks until the worker thread has terminated.
 */
void Framework::stop() 
{
    frameworkWorker->requestHalt();
    
    if (evaluateFitnessWorker != NULL) 
    {
        evaluateFitnessWorker->requestHalt();
        evaluateFitnessWorkerThread->wait();
        //we don't have to delete evaluateFitnessWorker, because it
        //is being stored inside a reference counted object.  See
        //ZThread::Thread constructed with a runnable for more info.  
        delete evaluateFitnessWorkerThread;
    }
    
    if (crossoverWorker != NULL) 
    {
        crossoverWorker->requestHalt();
        crossoverWorkerThread->wait();
        //we don't have to delete crossoverWorker, because it
        //is being stored inside a reference counted object.  See
        //ZThread::Thread constructed with a runnable for more info.
        delete crossoverWorkerThread;
    }
    
    // We don't have to delete the runnable used in the workerThread constructor
    // because it's a reference counted object.
    workerThread->wait();
    delete workerThread;
}

void Framework::limitedRun(int numSteps)
{   
    limitedRunFlag = true;
    targetGenerationCount = numSteps;
    
    start();
    workerThread->wait();
    stop();
    
}


vector<Solution*> Framework::getBestSolutions() 
{
    bestSolutionsMutex.acquire();
    priority_queue< Solution*, vector<Solution*>, SolutionPtrGTComparator> tmp(bestSolutions);
    vector<Solution*> returnMe;
    
    while (tmp.size() > 0) 
    {
        returnMe.insert(returnMe.begin(), (tmp.top())->clone());
        tmp.pop();
    }
    
    bestSolutionsMutex.release();
    return returnMe;
}

void Framework::getFitnessHistory(list<double> &avgFitness, list<double> &topFitness, unsigned int &generationNumber)
{
    historyMutex.acquire();
    generationNumber = generationCount;
    avgFitness = avgFitnessHistory;    
    topFitness = topFitnessHistory;    
    historyMutex.release();
}

void Framework::getBriefFitnessHistory(double &avgFitness, double &topFitness, unsigned int &generationNumber) {
    historyMutex.acquire();
    generationNumber = generationCount;
    avgFitness = avgFitnessHistory.front();    
    topFitness = topFitnessHistory.front();    
    historyMutex.release();
}

void Framework::preloadEvaluateFitnessJobs() 
{
    unassignedEvaluateFitnessJobsMutex.acquire();
    for (unsigned int i = 0; i < populationSize; i++ )
    {
        EvaluateFitnessJob* job = new EvaluateFitnessJob(newPopulation[i]); 
        unassignedEvaluateFitnessJobs.push_back(job);
    }
    unassignedEvaluateFitnessJobsMutex.release();        
}

void Framework::preloadBestSolutions() 
{
    vector<Solution*> tmpPopulation;
    
    //load the bestSolutions priority-queue with an initial set of useless objects.  
    
    //doing so is sort of bad form, but it allows us to optimize away a few
    //if statements in our primary loop, and better ugly here than ugly there.
    simulation->generateRandomPopulation(tmpPopulation, topResultsSize);
    
    for (unsigned int x = 0; x < tmpPopulation.size(); x++ )
        bestSolutions.push(tmpPopulation[x]);
}

void Framework::preloadHistory() 
{
    //load the history lists with an initial set of useless values.  
    //doing so is sort of bad form, but it allows us to optimize away a few
    //if statements in our primary loop, and better ugly here than ugly there.
    for (unsigned int x = 0; x < historySize; x++ ) 
    { 
        topFitnessHistory.push_front(0.0);
        avgFitnessHistory.push_front(0.0);
    }
}

void Framework::registerWorker(CrossoverWorker *newCWorker)
{
    crossoverWorkersMutex.acquire();
    if (crossoverWorkers.find(newCWorker) == crossoverWorkers.end())
    {
        crossoverWorkers.insert(newCWorker);
        crossoverWorkersMutex.release();
    }
    else
    {
        crossoverWorkersMutex.release();
        throw Worker_already_registered_exception();
    }
    
    newCWorker->setFramework(this);
    newCWorker->setCrossover(crosser);
}

void Framework::registerWorker(EvaluateFitnessWorker *newEFWorker)
{
    evaluateFitnessWorkersMutex.acquire();
    if (evaluateFitnessWorkers.find(newEFWorker) == evaluateFitnessWorkers.end())
    {
        evaluateFitnessWorkers.insert(newEFWorker);
        evaluateFitnessWorkersMutex.release();
    }
    else
    {
        evaluateFitnessWorkersMutex.release();
        throw Worker_already_registered_exception();
    }
    
    newEFWorker->setFramework(this);
    newEFWorker->setEnvironment(simulation);
}

void Framework::unregisterWorker(CrossoverWorker *worker)
{
    worker->requestHalt();
    
    crossoverWorkersMutex.acquire();
    crossoverWorkers.erase(worker);
    crossoverWorkersMutex.release();
}

void Framework::unregisterWorker(EvaluateFitnessWorker *worker)
{
    worker->requestHalt();
    
    evaluateFitnessWorkersMutex.acquire();
    evaluateFitnessWorkers.erase(worker);
    evaluateFitnessWorkersMutex.release();
}

void Framework::handleWorkerDone(CrossoverWorker* const worker )
{
    CrossoverJob *job = worker->getJob();
    
    Solution *solution1 = job->getChild1();
    Solution *solution2 = job->getChild2();
    
    newPopulationMutex.acquire();
    newPopulation.push_back(solution1);
    newPopulation.push_back(solution2);
    newPopulationMutex.release();
    
    EvaluateFitnessJob *evalJob1 = new EvaluateFitnessJob(solution1);
    EvaluateFitnessJob *evalJob2 = new EvaluateFitnessJob(solution2);
    
    delete job;
    
    unassignedEvaluateFitnessJobsMutex.acquire();
    unassignedEvaluateFitnessJobs.push_back(evalJob1);
    unassignedEvaluateFitnessJobs.push_back(evalJob2);
    unassignedEvaluateFitnessJobsMutex.release();
}

void Framework::handleWorkerDone(EvaluateFitnessWorker* const worker)
{
    EvaluateFitnessJob *job = worker->getJob();
    Solution* solution = job->getSolution();
    double fitness = job->getFitness();
    
    solution->setFitness(fitness);
     
    //see if the value needs to be inserted into the bestSolutions collection    
    bestSolutionsMutex.acquire();
    if (fitness > bestSolutions.top()->getFitness()) 
    {
        bestSolutions.push(solution->clone());
        delete bestSolutions.top();
        bestSolutions.pop();
    }
    bestSolutionsMutex.release();
    
    //clean up the job
    delete job;
    
    completedEvaluateFitnessJobsMutex.acquire();
    ++completedEvaluateFitnessJobs;
    completedEvaluateFitnessJobsMutex.release();
}

CrossoverJob *Framework::getCrossoverJob(CrossoverWorker *worker)
{
//    cout << "unassignedCrossoverJobs size: " << unassignedCrossoverJobs.size() << endl;
    crossoverWorkersMutex.acquire();
    if (crossoverWorkers.find(worker) == crossoverWorkers.end())
    {
	    crossoverWorkersMutex.release();
        throw Worker_not_registered_exception();
    }
    crossoverWorkersMutex.release();
    
    unassignedCrossoverJobsMutex.acquire();
    if (unassignedCrossoverJobs.empty())
    {
        unassignedCrossoverJobsMutex.release();
        return NULL;
    }
    
    CrossoverJob *job = unassignedCrossoverJobs.front();
    unassignedCrossoverJobs.pop_front();
    unassignedCrossoverJobsMutex.release();
    
    return job;
}

EvaluateFitnessJob *Framework::getEvaluateFitnessJob(EvaluateFitnessWorker *worker)
{
//    cout << "Unassigned EF jobs size: " << unassignedEvaluateFitnessJobs.size() << endl;
    evaluateFitnessWorkersMutex.acquire();
    if (evaluateFitnessWorkers.find(worker) == evaluateFitnessWorkers.end())
    {
        evaluateFitnessWorkersMutex.release();
        throw Worker_not_registered_exception();
    }
    evaluateFitnessWorkersMutex.release();
    
    unassignedEvaluateFitnessJobsMutex.acquire();
    if (unassignedEvaluateFitnessJobs.empty())
    {
	    unassignedEvaluateFitnessJobsMutex.release();
        return NULL;
    }
        
    EvaluateFitnessJob *job = unassignedEvaluateFitnessJobs.front();
    unassignedEvaluateFitnessJobs.pop_front();
    unassignedEvaluateFitnessJobsMutex.release();
    
    return job;
}
